package org.skycastle.megastructure

import com.jme.math.{Vector3f, Quaternion}
import com.jme.scene.{Node, Spatial}
import com.jme.system.DisplaySystem
import space.Space

/**
 * Something visible located in the game world.
 *
 * @author Hans Haggstrom
 */

class Entity( initialAppearance : Appearance ) {

  private var currentSpace : Space = null
  def space : Space = currentSpace
  def setSpace( space : Space) { currentSpace = space } 

  final def appearance : Appearance = initialAppearance

  final def getSpatial( display : DisplaySystem ) = appearance.getSpatial( display )

  private val rotation = new Quaternion()
  private val position = new Vector3f()
  private val velocity = new  Vector3f()
  private val acceleration = new  Vector3f()
  private val force = new  Vector3f()
  private var radius : Float = 1f
  private var mass_kg : Float = 1f

  private val v = new Vector3f()
  private val q = new Quaternion()
  private var creationTime_ms : Long = 0
  private var creationTimeInitialized = false

  def age_s = (space.getUniverse.timer.getTime - creationTime_ms) * 0.001f

  def update( timeSinceLastTime_s : Float ) {

    if (!creationTimeInitialized && space != null && space.getUniverse != null)
      creationTime_ms = space.getUniverse.timer.getTime 

  }


  /**
   * The approximate radius of this Entity.
   */
  def getRadius = radius

  /**
   * The mass of this Entity
   */
  def getMass_kg = mass_kg

  /**
   *   Calculates acceleration, updates velocity and position.
   */
  def simulatePhysics( duration_s : Float, externalForces : Vector3f, airResistance : Float ) {
    require( externalForces != null )

    force.zero
    force addLocal acceleration
    force addLocal externalForces

    velocity.scaleAdd( duration_s, force, velocity )
    velocity.multLocal( 1.0f - airResistance * duration_s )

    position.scaleAdd( duration_s, velocity, position )

    update3DModel()
  }


  def getPosition( out : Vector3f ) {
    require( out != null )

    out.zero
    out addLocal position
  }

  def getVelocity( out : Vector3f ) {
    require( out != null )

    out.zero
    out addLocal velocity
  }

  def setPosition( v : Vector3f ) {
    require( v != null )

    position.zero
    position addLocal v

    update3DModel()
  }

  def setAcceleration( v : Vector3f ) {
    require( v != null )

    acceleration.zero
    acceleration addLocal v
  }

  /**
   *
   */
  def setRelativeVelocity( v : Vector3f ) {
    require( v != null )

    velocity.zero
    velocity addLocal v

    // TODO: Check: should we apply this inversely?
    rotation multLocal velocity
  }


  /**
   * Set internal force, first transforming it according to the object rotation (positive x axis = direction of heading)
   */
  def setRelativeAcceleration( v : Vector3f ) {
    require( v != null )

    acceleration.zero
    acceleration addLocal v

    // TODO: Check: should we apply this inversely?
    rotation multLocal acceleration
  }

  def rotate( rotationChange : Quaternion ) {
    require( rotationChange != null )

    rotation multLocal rotationChange
  }

  def getRotation( rotationOut : Quaternion ) {
    rotationOut.set( rotation )
  }

  def getHeading( headingOut : Vector3f ) {

    // Transform (1,0,0) with the rotation

    headingOut.x = 1
    headingOut.y = 0
    headingOut.z = 0

    rotation multLocal headingOut
  }

  def setRotation( q : Quaternion ) {
    require( q != null )

    rotation.set( q )

    update3DModel()
  }

  def setRelativeAcceleration( x : Float, y : Float, z : Float ) {
    v.x = x
    v.y = y
    v.z = z

    setRelativeAcceleration( v )
  }

  def rotate( angle : Float, axisX: Float, axisY: Float, axisZ: Float ) {
    v.x = axisX
    v.y = axisY
    v.z = axisZ
    q.fromAngleAxis( angle, v )
    rotate( q )
  }



  def onCollision( collisionPosition : Vector3f,  surfaceNormal : Vector3f, collisionImpact : Float, otherObjectElasticity : Float ) {

    setPosition( collisionPosition )

    // TODO: Reflect properly, for now just bouncing back gently
    //velocity multLocal -0.5f
    velocity.y *= -0.5f
  }

  private def update3DModel(){
    val spatial: Spatial = appearance.getSpatialIfExists
    if (spatial != null) {
      spatial.setLocalTranslation( position )
      spatial.setLocalRotation( rotation )
    }
  }


}